Getting Started with Brightway2

This is an introduction to Brightway2, an open source framework for Life Cycle Assessment. This notebook will cover the basics of databases and activities and looking at LCI databases and LCIA methods.

At the end of this notebook, you will be able to:

  • Import basic data like the biosphere database
  • Import and explore the FORWAST database

If you finish the notebook, you get a kitten.

This introduction is written in an Jupyter notebook, an online scientific notebook which combines, text, data, images, and programming. It is amazing, and could be a fantastic way to do and communicate advanced LCA work. See the documentation and a list of awesome examples. Please see the Brightway2 documentation for a complete list of example Brightway2 notebooks.

You should download this notebook and run it cell by cell - don't just read it on the web!

Starting at the beginning

Import brightway2.

In [1]:
from brightway2 import *
Warning: bad escape \s

Python 2 and 3

Brightway2 is compatible with both Python 2 and 3. If you are using Python 2, I strongly recommend you execute the following cell, which will make all your text string unicode without have to type the letter "u" in front of each text string. On Python 3, the following doesn't do anything - your text strings are unicode by default.

I won't include this cell in future notebooks, but you should copy and paste it if you will use Python 2.

In [2]:
from __future__ import unicode_literals, print_function

Projects

In Brightway2, a project is a separate directory with its own copies of LCI databases, LCIA methods, and any other data you use. Each research project or article should probably be its own project, so that any changes you want to make will not interfere with your other work.

The default project is called default:

In [3]:
projects.current
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Out[3]:
'default'

Each project is stored in a separate directory in a place in your filesystem reserved for application data. It varies depending on the operating system; on OS X, this is the Library directory in your user home directory:

In [4]:
projects.dir
Out[4]:
'/Users/cmutel/Library/Application Support/Brightway3/default.c21f969b5f03d33d43e04f8f136e7682'

However, you really need to care about the specifics.

We can create a new project:

In [5]:
projects.current = "BW2 introduction"

And list the available projects:

In [6]:
projects
Out[6]:
Brightway2 projects manager with 27 objects, including:
	3.2
	BW2 Tutorial 1
	BW2 Tutorial 4
	BW2 introduction
	CAES
	Class 1
	Class 2
	Class 3
	Class 5
	Comparative static LCA
Use `list(projects)` to get full list, `projects.report()` to get
	a report on all projects.

Getting basic data

Let's import some basic data - a database of elementary flows, some LCIA methods, and some metadata used for importing other databases:

In [7]:
bw2setup()
Creating default biosphere

Writing activities to SQLite3 database:
0%                          100%
[####                          ] | ETA[sec]: 1.207 
Applying strategy: normalize_units
Applying strategy: drop_unspecified_subcategories
Applied 2 strategies in 0.01 seconds
[##############################] | ETA[sec]: 0.000 
Total time elapsed: 0.696 sec
Title: Writing activities to SQLite3 database:
  Started: 03/14/2016 22:27:02
  Finished: 03/14/2016 22:27:03
  Total time elapsed: 0.696 sec
  CPU %: 91.500000
  Memory %: 1.409245
Created database: biosphere3
Creating default LCIA methods

Applying strategy: normalize_units
Applying strategy: set_biosphere_type
Applying strategy: drop_unspecified_subcategories
Applying strategy: link_iterable_by_fields
Applied 4 strategies in 2.17 seconds
Wrote 665 LCIA methods with 169551 characterization factors
Creating core data migrations

The iPython notebook by default prints all logged messages. On your machine, there might be messages tracking how long it took to download and process the biosphere and methods packages.

A biosphere dataset

The biosphere3 database was installed. It is called the biosphere3 database because elementary flow names are normalized to the ecoinvent 3 standard.

Let's see how many flows there are in this database, and then look at one random flow:

In [8]:
db = Database("biosphere3")
print("Number of flows in `biosphere3`:", len(db))
random_flow = db.random()
print(random_flow)
Number of flows in `biosphere3`: 4018
'o-Dichlorobenzene' (kilogram, None, ('water', 'ground-'))
In [9]:
print(random_flow['name'])
print(random_flow['unit'])
print(random_flow['categories'])
o-Dichlorobenzene
kilogram
('water', 'ground-')

Brightway2 uses keys to identify datasets. Each dataset is identified by a combination of its database and some unique code. The code can be anything - a number, a UUID, or just a name. All of the following would be valid keys:

("biosphere", "f66d00944691d54d6b072310b6f9de37")
("my new database", "building my dream house")
("skynet", 14832)
In [10]:
random_flow.key
Out[10]:
('biosphere3', '59af2a59-f4ed-43a0-b5ab-887de45b9571')

An LCIA method dataset

We also installed a large number of LCIA methods:

In [11]:
len(methods)
Out[11]:
665

Because LCIA methods have many different impact categories, they are identified not by a single label, but by a list of labels. Let's look at an example:

In [66]:
method_key = methods.random()
method_key
Out[66]:
('ReCiPe Endpoint (E,A)', 'human health', 'climate change, human health')

In this case, the LCIA method has three levels of specificity, from the general name (first level) to the specific impact category (last level). There is nothing magic about three levels - you could have one, or one hundred - but Brightway2 expects that LCIA methods will be a list of text labels ('like', 'this').

Note that method identifiers need to be a special kind of list that uses () instead of []. These are called tuples. To create a tuple with only one element, you need to add a comma, to distinguish it from a set of parentheses:

In [13]:
print (1 + 2)           # This is not a tuple
print (1,), type((1,))  # This is a tuple with one element
3
1
Out[13]:
(None, tuple)

We can load the method data, show a sample.

Method data has the format:

biosphere flow, numeric value, location

Where:

  • biosphere flow is a dataset from any database which is used as a biosphere flow.
  • numeric value can be either a fixed number or an uncertainty distribution.
  • location is optional; the default value is that this characterization factor is valid everywhere.

The method data format is pretty flexible, and the following are all acceptable:

[('biosphere', 'CO2'), 1.0],                                             # Default location
[('biosphere', 'CO2'), 1.0, 'Australia, mate!'],                         # Custom location
[('biosphere', 'CO2'), 1.0, ('Population density', 'Raster cell 4,2')],  # Location inside a geocollection
[('biosphere', 'CO2'), {'amount': 1.0, 'uncertainty type': 0}],          # Uncertain characterization factor

Geocollections are needed for regionalized LCA.

If you are wondering why we need to identify biosphere flows like ('biosphere', '2fe885840cebfcc7d56b607b0acd9359'), this is a good question! The short answer is that there is no single field that uniquely identifies biosphere flows or activities. The longer answer is in the manual.

Brightway2 is designed to be flexible enough for many different problems. Therefore, there are no limits on what constitutes a biosphere flow. Rather, anything that is linked to in a biosphere exchange will be put in the biosphere matrix. We installed a database called biosphere3, but you can define new flows in a database alongside process datasets, or create your own biosphere database.

In [67]:
method_data = Method(method_key).load()
print("Number of CFs:", len(method_data))
method_data[:20]
Number of CFs: 193
Out[67]:
[(('biosphere3', 'e259263c-d1f1-449f-bb9b-73c6d0a32a00'), 0.034243902439),
 (('biosphere3', '16eeda8a-1ea2-408e-ab37-2648495058dd'), 0.034243902439),
 (('biosphere3', 'aa7cac3a-3625-41d4-bc54-33e2cf11ec46'), 0.034243902439),
 (('biosphere3', '349b29d1-3e58-4c66-98b9-9d1a076efd2e'), 0.034243902439),
 (('biosphere3', 'f9749677-9c9f-4678-ab55-c607dfdc2cb9'), 0.034243902439),
 (('biosphere3', '5188df39-2bb8-4826-b469-fa9f86bacc09'), 0.342439243924),
 (('biosphere3', 'e1c597cc-14cb-4ebb-af07-7a93a5b77d34'), 0.034243902439),
 (('biosphere3', '6d89125e-e9b7-4d7e-a1fc-ada45dbd8815'), 0.034243902439),
 (('biosphere3', '78eb1859-abd9-44c6-9ce3-f3b5b33d619c'), 0.034243902439),
 (('biosphere3', 'e4e9febc-07c1-403d-8d3a-6707bb4d96e6'), 0.034243902439),
 (('biosphere3', 'e8787b5e-d927-446d-81a9-f56977bbfeb4'), 0.034243902439),
 (('biosphere3', '375bc95e-6596-4aa1-9716-80ff51b9da77'), -0.342439243924),
 (('biosphere3', '78259cf2-1bb2-41ec-941c-9d70909df094'), 0.318468292683),
 (('biosphere3', '98af3079-fb76-488d-8afe-713814e14d36'), 0.318468292683),
 (('biosphere3', 'fa9b9736-6de8-4bd6-ba22-1bd78c798373'), 0.318468292683),
 (('biosphere3', 'fbf6ab3a-74e3-4cbe-b61d-959728bdce46'), 0.318468292683),
 (('biosphere3', '4cadbc9f-0472-4bb9-a942-df5b90e37878'), 0.318468292683),
 (('biosphere3', '81305cda-63f1-4c1a-99c6-2c2568550ec4'), 5.23931707317),
 (('biosphere3', '473826ae-125a-4b02-8c8e-c84322491d80'), 5.23931707317),
 (('biosphere3', 'afd6d670-bbb0-4625-9730-04088a5b035e'), 5.23931707317)]

Importing the FORWAST LCI database

We will use the FORWAST database, as it is both a high quality, comprehensive LCI database, and freely available. FORWAST is a physical MRIO table for Europe. It can be downloaded directly from the 2.-0 website.

The following cell will download and install the FORWAST database. Note that an internet connection is required.

In [15]:
import zipfile
import os
from bw2data.utils import download_file

filepath = download_file("forwast.bw2package.zip", url="http://lca-net.com/wp-content/uploads/")
dirpath = os.path.dirname(filepath)
zipfile.ZipFile(filepath).extractall(dirpath)
BW2Package.import_file(os.path.join(dirpath, "forwast.bw2package"))
Writing activities to SQLite3 database:
0%                          100%
[##############################] | ETA[sec]: 0.000 
Total time elapsed: 5.360 sec
Title: Writing activities to SQLite3 database:
  Started: 03/14/2016 22:28:25
  Finished: 03/14/2016 22:28:31
  Total time elapsed: 5.360 sec
  CPU %: 85.800000
  Memory %: 3.499031
Out[15]:
[Brightway2 SQLiteBackend: forwast]

Searching datasets

By default, every database is added to a search engine powered by Whoosh. Searching covers the following data fields:

  • name
  • comment
  • product
  • categories
  • location

Searching is done by using the Database.search method.

In [16]:
Database("forwast").search("food")
Out[16]:
['_26 Food preparations n.e.c., EU27' (kilogram, GLO, ['Input Output', 'EU27 2003']),
 '_26 Food preparations n.e.c., DK' (kilogram, GLO, ['Input Output', 'Denmark 2003']),
 '107 Waste treatment, Landfill of waste, Food, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003']),
 '116 Waste treatment, Landfill of waste, Food, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003']),
 '112 Waste treatment, Composting of food waste, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003']),
 '109 Waste treatment, Biogasification of food waste, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003']),
 '101 Waste treatment, Incineration of waste, Food, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003']),
 '_92 Waste treatment, Incineration of waste, Food, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003']),
 '100 Waste treatment, Biogasification of food waste, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003']),
 '103 Waste treatment, Composting of food waste, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003']),
 '105 Waste treatment, Waste water treatment, food, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003']),
 '114 Waste treatment, Waste water treatment, food, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])]

Searches can also be filtered (where only the results that meet the specified criteria are included) or masked (where results that meet the specified criteria are excluded).

By default we return 25 search results, but this can be changed by specifying the limit keyword argument.

You can also use * wild cards in search queries:

In [17]:
Database("biosphere3").search("carb*", limit=10)
Out[17]:
['Carbon-14' (kilo Becquerel, None, ('water', 'ground-')),
 'Ammonium carbonate' (kilogram, None, ('air', 'lower stratosphere + upper troposphere')),
 'Boron carbide' (kilogram, None, ('air', 'urban air close to ground')),
 'Boron carbide' (kilogram, None, ('air',)),
 'Carbon monoxide, from soil or biomass stock' (kilogram, None, ('air', 'urban air close to ground')),
 'Carbon disulfide' (kilogram, None, ('air', 'low population density, long-term')),
 'Carboxylic acids, unspecified' (kilogram, None, ('water', 'ocean')),
 'TOC, Total Organic Carbon' (kilogram, None, ('water', 'ground-, long-term')),
 'Boron carbide' (kilogram, None, ('water', 'ground-, long-term')),
 'Carbon-14' (kilo Becquerel, None, ('water', 'surface water'))]

You can specify inclusion or exclusion criteria for fields with filter and mask:

In [18]:
Database("biosphere3").search("carbon", filter={"categories": 'forestry'})
Excluding 77 filtered results
Out[18]:
['Carbon' (kilogram, None, ('soil', 'forestry')),
 'Carbon dioxide, to soil or biomass stock' (kilogram, None, ('soil', 'forestry'))]
In [19]:
Database("biosphere3").search("carbon", limit=10, mask={"categories": 'forestry'})
Excluding 2 filtered results
Out[19]:
['Carbon' (kilogram, None, ('soil',)),
 'Carbon' (kilogram, None, ('soil', 'industrial')),
 'Carbon' (kilogram, None, ('soil', 'agricultural')),
 'Carbon-14' (kilo Becquerel, None, ('water', 'ground-')),
 'Carbon disulfide' (kilogram, None, ('air', 'low population density, long-term')),
 'Carbon-14' (kilo Becquerel, None, ('water', 'surface water')),
 'Carbon-14' (kilo Becquerel, None, ('water', 'ground-, long-term')),
 'Carbon disulfide' (kilogram, None, ('air', 'non-urban air or from high stacks')),
 'Carbon disulfide' (kilogram, None, ('water', 'surface water')),
 'Carbon disulfide' (kilogram, None, ('air', 'lower stratosphere + upper troposphere'))]

Finally, you can facet search results by another field. This is a bit complicated, so test your queries before assuming certain behaviour.

In [20]:
sr = Database("biosphere3").search("carbon", facet="categories")
for key, value in sr.items():
    print("Facet term:", key, "\n\t", value[:3], "\n")
Facet term: air 
	 ['Carbon-14' (kilo Becquerel, None, ('air',)), 'Carbon disulfide' (kilogram, None, ('air',)), 'Carbon monoxide, fossil' (kilogram, None, ('air',))] 

Facet term: term 
	 ['Carbon disulfide' (kilogram, None, ('air', 'low population density, long-term')), 'Carbon-14' (kilo Becquerel, None, ('air', 'low population density, long-term')), 'Carbon dioxide, fossil' (kilogram, None, ('air', 'low population density, long-term'))] 

Facet term: upper 
	 ['Carbon disulfide' (kilogram, None, ('air', 'lower stratosphere + upper troposphere')), 'Carbon-14' (kilo Becquerel, None, ('air', 'lower stratosphere + upper troposphere')), 'Carbon dioxide, fossil' (kilogram, None, ('air', 'lower stratosphere + upper troposphere'))] 

Facet term: resource 
	 ['Carbon dioxide, in air' (kilogram, None, ('natural resource', 'in air')), 'Carbon, organic, in soil or biomass stock' (kilogram, None, ('natural resource', 'in ground'))] 

Facet term: urban 
	 ['Carbon disulfide' (kilogram, None, ('air', 'non-urban air or from high stacks')), 'Organic carbon' (kilogram, None, ('air', 'urban air close to ground')), 'Carbon-14' (kilo Becquerel, None, ('air', 'non-urban air or from high stacks'))] 

Facet term: indoor 
	 ['Carbon monoxide, from soil or biomass stock' (kilogram, None, ('air', 'indoor')), 'Carbon dioxide, from soil or biomass stock' (kilogram, None, ('air', 'indoor'))] 

Facet term: water 
	 ['Carbon-14' (kilo Becquerel, None, ('water', 'ground-')), 'Carbon-14' (kilo Becquerel, None, ('water', 'surface water')), 'Carbon-14' (kilo Becquerel, None, ('water', 'ground-, long-term'))] 

Facet term: soil 
	 ['Carbon' (kilogram, None, ('soil',)), 'Carbon' (kilogram, None, ('soil', 'industrial')), 'Carbon' (kilogram, None, ('soil', 'agricultural'))] 

Changing iteration order

You can also change the way that processes are iterated over in the database. The default order is random:

In [21]:
db = Database("forwast")

def print_10(db):
    for i, x in enumerate(db):
        if i < 10:
            print(x)
        else:
            break
            
print_10(db)
'_35 Printed matter and recorded media, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'105 Waste treatment, Incineration of waste, Glass' (kilogram, inert, EU27, ['Input Output', 'EU27 2003'])
'_43 Glass, mineral wool and ceramic goods, virgin, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'_18 Meat products, Poultry and meat n.e.c., DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'_25 Animal feeds, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'_32 Wood products, except furniture, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])
'_29 Textiles, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'127 Household use, Health care, DK' (EUR2003, GLO, ['Input Output', 'Denmark 2003'])
'__7 Horticulture, orchards etc., DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'_61 Instruments, medical, precision, optical, clocks, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
In [22]:
print_10(db)
'_58 Office machinery and computers, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'126 Household use, Education, DK' (EUR2003, GLO, ['Input Output', 'Denmark 2003'])
'124 Household use, Clothing, DK' (EUR2003, GLO, ['Input Output', 'Denmark 2003'])
'_36 Refined petroleum products and fuels, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'_79 Buildings, non-residential, EU27' (EUR2003, GLO, ['Input Output', 'EU27 2003'])
'__4 Grain crops, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'_53 Copper basic, virgin, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'_70 Buildings, non-residential, DK' (EUR2003, GLO, ['Input Output', 'Denmark 2003'])
'_21 Fruits and vegetables, processed, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])
'119 Waste treatment, Landfill of waste, Slag' (kilogram, ash, DK, ['Input Output', 'Denmark 2003'])

You can sort by location, name, product (reference product), or type, by specifying the order_by property.

In [23]:
db.order_by = "name"
print_10(db)
'100 Health and social work, EU27' (EUR2003, GLO, ['Input Output', 'EU27 2003'])
'100 Waste treatment, Biogasification of food waste, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'101 Waste treatment, Biogasification of paper, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'101 Waste treatment, Incineration of waste, Food, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])
'102 Waste treatment, Biogasification of sewage slugde, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'102 Waste treatment, Incineration of waste, Paper, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])
'103 Waste treatment, Composting of food waste, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'103 Waste treatment, Incineration of waste, Plastic, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])
'104 Waste treatment, Composting of paper and wood, DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])
'104 Waste treatment, Incineration of waste, Metals, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])

If the above seems wrong, remember that the names start with 100, 101, etc.

Set .order_by = None to remove any ordering.

In [24]:
db.order_by = None

Because accessing activities in the database is quite fast, you can also filter the activities you want by just iterating over the entire database:

In [25]:
my_activities = [x for x in db if 'Electr' in x['name']]
my_activities
Out[25]:
['_68 Electrical machinery n.e.c., EU27' (kilogram, GLO, ['Input Output', 'EU27 2003']),
 '_66 Electricity, steam and hot water, DK' (kilowatt hour, GLO, ['Input Output', 'Denmark 2003']),
 '_75 Electricity, steam and hot water, EU27' (kilowatt hour, GLO, ['Input Output', 'EU27 2003']),
 '_59 Electrical machinery n.e.c., DK' (kilogram, GLO, ['Input Output', 'Denmark 2003'])]

Basic LCA calculations

Let's pick and random process and LCIA method:

In [26]:
process = Database("forwast").random()
process
Out[26]:
'_89 Post and telecommunication, EU27' (EUR2003, GLO, ['Input Output', 'EU27 2003'])

A brief review of LCA calculations:

In matrix-based LCA, we construct a technosphere matrix, which describes the inputs needed to produce different products (e.g. cars need metal and electricity), and a biosphere matrix, which describes the emissions and resource consumption associated with the production of each product (e.g. car manufacturing releases air emissions). These two matrices come from the life cycle inventory database(s). We also have a functional unit, which is what we are trying to assess, e.g. one car. We then calculate the life cycle inventory (LCI) by first solving the linear system of the technosphere matrix and the functional unit, and then by multiplying the biosphere matrix.

To do life cycle impact assessment (LCIA), we multiply the life cycle inventory by a matrix of characterization factors, which tell how bad different emissions and resource consumptions are.

For more details on the math, see the manual.

So, our first step is to specify the functional unit, which is relatively easy:

In [27]:
functional_unit = {process: 1}

We can then instantiate our LCA object.

In [68]:
lca = LCA(functional_unit, method_key)

And do the LCI and LCIA calculations:

In [69]:
lca.lci()
lca.lcia()

Finally, we can print the LCA score:

In [70]:
lca.score
Out[70]:
0.009484271945002704

You can reuse the same matrices but change the functional unit by using the redo_lci and redo_lcia functions:

In [71]:
new_process = Database("forwast").random()
print(new_process)
lca.redo_lcia({new_process: 1})
lca.score
'_63 Copper, after first processing, EU27' (kilogram, GLO, ['Input Output', 'EU27 2003'])
Out[71]:
0.2536993395051675

Looking into the LCA object

Let's see what is in this LCA thing, anyway. Put your cursor in the following cell and hit tab:

In [ ]:
lca.

So, there is a lot. Let's look at a few things:

  • The technosphere matrix
In [72]:
lca.technosphere_matrix
Out[72]:
<277x277 sparse matrix of type '<class 'numpy.float64'>'
	with 36825 stored elements in Compressed Sparse Row format>
  • The biosphere matrix
In [73]:
lca.biosphere_matrix
Out[73]:
<20x277 sparse matrix of type '<class 'numpy.float64'>'
	with 2170 stored elements in Compressed Sparse Row format>
  • The characterization matrix
In [74]:
lca.characterization_matrix
Out[74]:
<20x20 sparse matrix of type '<class 'numpy.float64'>'
	with 3 stored elements in Compressed Sparse Row format>

Graphing matrices

Sometimes it can be helpful to visualize both the inputs and calculation results.

In [36]:
%matplotlib inline
Warning: metadata {'config': True} was set from the constructor.  Metadata should be set using the .tag() method, e.g., Int().tag(key1='value1', key2='value2')
Warning: metadata {'config': True} was set from the constructor.  Metadata should be set using the .tag() method, e.g., Int().tag(key1='value1', key2='value2')
Warning: metadata {'config': True} was set from the constructor.  Metadata should be set using the .tag() method, e.g., Int().tag(key1='value1', key2='value2')
Warning: metadata {'config': True} was set from the constructor.  Metadata should be set using the .tag() method, e.g., Int().tag(key1='value1', key2='value2')
Warning: metadata {'config': True} was set from the constructor.  Metadata should be set using the .tag() method, e.g., Int().tag(key1='value1', key2='value2')
Warning: _[traitname]_default handlers are deprecated: use default decorator instead
Warning: _[traitname]_changed handlers are deprecated: use observe and unobserve instead
In [37]:
from bw2analyzer.matrix_grapher import SparseMatrixGrapher

First, let's look at the technosphere matrix.

You can pass in a filename to get a higher resolution figure saved as a file.

In [38]:
SparseMatrixGrapher(lca.technosphere_matrix).ordered_graph()

Not so interesting - I am sure your inventory data will be much nicer. The problem is that this is an IO matrix, so there is a value at each point, even if it is small, and this graph only shows where values are or aren't zero. For example, here is the same graph for ecoinvent 3.2 (cutoff):

You can also graph the biosphere and any other LCA matrices.

Contribution analysis

We can calculate the most important activities and biosphere flows.

In [39]:
from bw2analyzer import ContributionAnalysis

Most important activities

In [75]:
ContributionAnalysis().annotated_top_processes(lca)
Out[75]:
[(0.10671241792318513, 1.3830288788118601, '_57 Copper basic, virgin, EU27'),
 (0.07394704900181974,
  5.3603838094053362,
  '_75 Electricity, steam and hot water, EU27'),
 (0.026353772441020381,
  0.31584191863112365,
  '_58 Recycling of copper basic, EU27'),
 (0.010672007403296485,
  0.15327650785887367,
  '_85 Land transport and transport via pipelines, EU27'),
 (0.0058203682442093023,
  1.0165185044531806,
  '_37 Refined petroleum products and fuels, EU27'),
 (0.0040377335485776078,
  1.7814138882784873,
  '_63 Copper, after first processing, EU27'),
 (0.0033893393905809898,
  1.395597144103552,
  '_11 Crude petroleum and natural gas, EU27'),
 (0.0032372259902631842, 2.6658278221512934, '_14 Copper from mine, EU27'),
 (0.0029664330774805529, 0.067185264305851727, '_53 Iron basic, virgin, EU27'),
 (0.0027871274645682683,
  0.1899268656103322,
  '_54 Recycling of iron basic, EU27'),
 (-0.0027310022943254778,
  -0.061384492529333673,
  '_59 Metals basic, n.e.c., virgin, EU27'),
 (0.0017484345768585899, 0.022414912652192396, '_86 Transport by ship, EU27'),
 (0.0015558716306916067, 0.047287545502670732, '_39 Fertiliser, N, EU27'),
 (0.0015408661466259068, 1.3685353099463269, '_10 Coal, lignite, peat, EU27'),
 (0.0014685403183037537,
  0.15147333416426897,
  '_61 Iron, after first processing, EU27'),
 (0.0012336454810141929,
  0.18185334948807749,
  '_80 Infrastructure, excluding buildings, EU27'),
 (0.00090139014382475925, 0.10185547832666511, '_13 Bauxite from mine, EU27'),
 (0.00089345391836654861, 0.39739448309467185, '_76 Gas, EU27'),
 (-0.00084800100757784246,
  -0.011924400179835719,
  '_64 Metals n.e.c., after first processing, EU27'),
 (0.00066356434516174766,
  0.019456879339591761,
  '_41 Plastics basic, virgin, EU27'),
 (0.00058341160765022543, 0.015314506217335613, '_87 Air transport, EU27'),
 (-0.00053337018576259565,
  -0.011429422138285674,
  '_55 Aluminium basic, virgin, EU27'),
 (0.000456194862649706,
  0.0060068232516261701,
  '103 Waste treatment, Incineration of waste, Plastic, EU27'),
 (0.00037970819581853563,
  0.050675767350437731,
  '_66 Machinery and equipment n.e.c., EU27'),
 (0.00037765989293519396,
  0.054169476962014254,
  '_65 Fabricated metal products, except machinery, EU27')]

Most important biosphere flows

In [76]:
ContributionAnalysis().annotated_top_emissions(lca)
Out[76]:
[(0.25026778993000831, 7.3083895321675412, 'Carbon dioxide, fossil'),
 (0.0018158959273344637, 0.00034659020403294156, 'Dinitrogen monoxide'),
 (0.0016156536478247475, 0.0062079956885421721, 'Methane, fossil'),
 (0.0, 0.013712909165644506, 'Nitrogen oxides'),
 (0.0, 0.68274824426864633, 'Gas, natural, in ground'),
 (0.0,
  0.0037581188057636042,
  'NMVOC, non-methane volatile organic compounds, unspecified origin'),
 (0.0, 0.051223594215921452, 'Iron, 46% in ore, 25% in crude ore, in ground'),
 (0.0, 7.2459120183350216e-05, 'Lead, Pb 0.014%, in mixed ore, in ground'),
 (0.0, 0.015508160343417886, 'Carbon monoxide, fossil'),
 (0.0, 0.87481427731851147, 'Oil, crude, in ground'),
 (0.0, 0.00017785924303948472, 'Ammonia'),
 (0.0, 0.088482702206113997, 'Carbon dioxide, in air'),
 (0.0, 1.2313910081840855, 'Sand, unspecified, in ground'),
 (0.0, 1.3753474723935553, 'Coal, hard, unspecified, in ground'),
 (0.0,
  0.034387380582497677,
  'Nickel, 1.98% in silicates, 1.04% in crude ore, in ground'),
 (0.0,
  0.73171113771002272,
  'Copper, 1.13% in sulfide, Cu 0.76% and Ni 0.76% in crude ore, in ground'),
 (0.0, 0.16223941901467354, 'Carbon dioxide, non-fossil'),
 (0.0, 0.023822663139183308, 'Sulfur dioxide'),
 (0.0, 0.069890633126106549, 'Aluminium, in ground'),
 (0.0, 0.0002235179620606673, 'Zinc, Zn 0.63%, in mixed ore, in ground')]

Monte Carlo LCA

Unfortunately, the forwast database doesn't unclude uncertainty. Let's put some in anyways, using the utility function uncertainify.

In [42]:
from bw2data.utils import uncertainify
from stats_arrays import NormalUncertainty
In [43]:
uncertain_db = Database("forwast uncertain +")
uncertain_db.register()
uncertain_db.write(
    uncertain_db.relabel_data(
        uncertainify(
            Database("forwast").load(), 
            NormalUncertainty
        ), 
        "forwast uncertain +" 
    )
)
Writing activities to SQLite3 database:
0%                          100%
[##############################] | ETA[sec]: 0.000 
Total time elapsed: 5.856 sec
Title: Writing activities to SQLite3 database:
  Started: 03/14/2016 22:31:48
  Finished: 03/14/2016 22:31:53
  Total time elapsed: 5.856 sec
  CPU %: 81.300000
  Memory %: 2.965212

We can now calculate some Monte Carlo iterations for a random activity.

In [77]:
mc = MonteCarloLCA(demand={uncertain_db.random(): 1}, method=method_key)
mc.load_data()
for x in range(10):
    print(mc.next())
0.0031529985519416722
0.0028566662868159997
0.002740671272351993
0.0026321500741346973
0.003267566048312241
0.002819831082945057
0.0021000581031175355
0.0031783104918298557
0.0028480430158358203
0.003351686494958392

That't it! Here is your promised kitten:

In [45]:
from IPython.display import Image
import random
dimensions = sorted((int(random.random() * 600 + 200), int(random.random() * 600 + 200)))
Image(url="http://placekitten.com/{}/{}/".format(*dimensions))
Out[45]:
In [ ]: